﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;

using LowLevelGraphics.ImageFormats.SVG.Shapes;

using Rectangle = LowLevelGraphics.ImageFormats.SVG.Shapes.Rectangle;

namespace LowLevelGraphics.ImageFormats.SVG
{
    /// <summary>
    /// 
    /// </summary>
    public class TypeFactory
    {
        private static string m_sCommandString = "(m|M|Z|L|H|V|C|c|S|Q|T|A|z)";

        /// <summary>
        /// Creates the specified type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="_xmlNode">The _XML node.</param>
        /// <returns></returns>
        public static Element Create(Type type, XmlNode _xmlNode)
        {
            Element element = null;
            if (type == typeof(Path))
            {
                element = CreatePath(_xmlNode, element);
            }
            else if (type == typeof(Circle))
            {
                element = CreateCircle(_xmlNode, element);
            }
            else if (type == typeof(Ellipse))
            {
                element = CreateEllipse(_xmlNode, element);
            }
            else if (type == typeof(Line))
            {
                element = CreateLine(_xmlNode, element);
            }
            else if (type == typeof(Polygon))
            {
                element = CreatePolygon(_xmlNode, element);
            }
            else if (type == typeof(Polyline))
            {
                element = CreatePolyline(_xmlNode, element);
            }
            else if (type == typeof(Text))
            {
                element = CreateText(_xmlNode, element);
            }
            else if (type == typeof(Rectangle))
            {
                element = CreateRectangle(_xmlNode, element);
            }

            return element;
        }

        /// <summary>
        /// Creates the path.
        /// </summary>
        /// <param name="_xmlNode">The _XML node.</param>
        /// <param name="element">The element.</param>
        /// <returns></returns>
        private static Element CreatePath(XmlNode _xmlNode, Element element)
        {
            string sD = _xmlNode.Attributes["d"].Value; // Path Definition

            XmlAttribute xmlAttributeStyle = _xmlNode.Attributes["style"];

            Style style = null;
            if (xmlAttributeStyle != null)
            {
                style = CreateStyle(xmlAttributeStyle.Value);
            }
            else
            {
                style = CreateStyle(_xmlNode);
            }

            string[] aElement = Regex.Split(sD, m_sCommandString);

            //Parse Commands
            PointF ptStartPoint = new PointF(0, 0);

            bool bIsPolygon = IsPolygonalPath(aElement);
            
            PointF ptCurrentPoint = Point.Empty;

            GraphicsPath graphicsPath = new GraphicsPath();

            if (bIsPolygon)
            {
                ptCurrentPoint = CreatePolygonalPath(m_sCommandString, aElement, ptCurrentPoint, graphicsPath);
            }
            else
            {
                ptCurrentPoint = CreateDefaultPath(m_sCommandString, aElement, ptCurrentPoint, graphicsPath);
            }

            Path path = new Path();
            path.GraphicsPath = graphicsPath;
            path.Style = style;
            element = path;
            return element;
        }

        /// <summary>
        /// Create Polygonal Path
        /// i.e. if only L, M and z commands are found it is assumpted we want to show a polygon
        /// </summary>
        /// <param name="sCommandString"></param>
        /// <param name="aElement"></param>
        /// <param name="ptCurrentPoint"></param>
        /// <param name="graphicsPath"></param>
        /// <returns></returns>
        private static PointF CreatePolygonalPath(string sCommandString, string[] aElement, PointF ptCurrentPoint, GraphicsPath graphicsPath)
        {
            graphicsPath.StartFigure();
            List<PointF> aPoint = new List<PointF>();
            int i = 0;
            for (i = 1; i < aElement.Length - 1; i++)
            {
                string sChar = aElement[i].ToString();
                if (m_sCommandString.Contains(sChar)) continue;
                sChar = sChar.Trim();
                List<PointD> aPointTemp = GetCoordinatesFromElement(sChar);
                aPoint.Add(PointD.Ceiling(aPointTemp[0]));

            }
            graphicsPath.AddPolygon(aPoint.ToArray());
            graphicsPath.CloseFigure();
            return PointF.Empty;
        }

        /// <summary>
        /// Create Default Path, this is used if it should be no polygon
        /// </summary>
        /// <param name="sCommandString"></param>
        /// <param name="aElement"></param>
        /// <param name="ptCurrentPoint"></param>
        /// <param name="graphicsPath"></param>
        /// <returns></returns>
        private static PointF CreateDefaultPath(string sCommandString, string[] aElement, PointF ptCurrentPoint, GraphicsPath graphicsPath)
        {
            int i = 0;
            for (i = 1; i < aElement.Length - 1; i++)
            {
                string sChar = aElement[i].ToString();
                if (sCommandString.IndexOf(sChar) != -1)
                {
                    i++;
                    string sElement = aElement[i];
                    sElement = sElement.Trim();

                    if (string.IsNullOrEmpty(sElement) && sChar != "Z") continue;
                    List<PointD> aPoint = null;
                    //if (sChar != "Zz")
                    //{
                    if (sChar != "A")
                    {
                        aPoint = GetCoordinatesFromElement(sElement);
                    }
                    else
                    {
                        GetArcCoordinatesFromElement(sElement);
                    }
                    //}
                    //Move to
                    if (sChar == "M")
                    {
                        ptCurrentPoint = PointD.Ceiling(aPoint[0]);
                    }
                    //Line to
                    else if (sChar == "L")
                    {
                        graphicsPath.AddLine(ptCurrentPoint, PointD.Ceiling(aPoint[0]));
                        if (aPoint.Count > 1) ptCurrentPoint = PointD.Ceiling(aPoint[1]);
                    }
                    //horizontal line to
                    else if (sChar == "H")
                    {
                    }
                    //vertical line to
                    else if (sChar == "V")
                    {
                    }
                    //curve to
                    else if (sChar == "C")
                    {
                        //graphicsPath.AddBezier(ptCurrentPoint, PointD.Ceiling(aPoint[0]), PointD.Ceiling(aPoint[1]), PointD.Ceiling(aPoint[2]));
                        graphicsPath.AddCurve(
                            new PointF[]{
                                PointD.Ceiling(aPoint[0]), 
                                PointD.Ceiling(aPoint[1]), 
                                PointD.Ceiling(aPoint[2])
                            });

                        ptCurrentPoint = PointD.Ceiling(aPoint[2]);
                    }
                    //quadratic bezier curve
                    else if (sChar == "Q")
                    {
                    }
                    //smooth quadratic bezier curve
                    else if (sChar == "T")
                    {
                    }
                    //elliptical Arc
                    else if (sChar == "A")
                    {
                        //graphicsPath.AddArc(RectangleF.FromLTRB(aPoint[0], aPoint[1], aPoint[2], aPoint[3], aPoint[4]), aPoint[5]);
                    }
                    //close path
                    else if (sChar == "Z")
                    {
                        //path.Style.FillColor = Color.Black;
                        graphicsPath.CloseFigure();
                    }
                    else if (sChar == "z")
                    {
                        graphicsPath.CloseFigure();
                    }
                }
            }
            return ptCurrentPoint;
        }

        /// <summary>
        /// Determines if the path should be added as polygon
        /// </summary>
        /// <param name="_aElement"></param>
        /// <returns></returns>
        private static bool IsPolygonalPath(string[] _aElement)        
        {
            string sAllowed = "M|L|z|Z|[0123456789]+,*| *";
            foreach (string sString in _aElement)
            {
                if (string.IsNullOrEmpty(sString)) continue;
                Match match = Regex.Match(sString, sAllowed);
                if (match.Length == 0) return false;
            }
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_sElement"></param>
        /// <returns></returns>
        public static List<PointD> GetArcCoordinatesFromElement(string _sElement)
        {
            return null;
        }

        /// <summary>
        /// Gets the coordinates from element.
        /// </summary>
        /// <param name="_sElement">The _s element.</param>
        /// <returns></returns>
        public static List<PointD> GetCoordinatesFromElement(string _sElement)
        {
            List<PointD> aPoint = new List<PointD>();

            PointD point = PointD.Empty;

            string[] aPointCoordinate = null;

            string sX = string.Empty;
            string sY = string.Empty;

            string[] aElement = _sElement.Split(' ');
            foreach (string sElement in aElement)
            {
                if (sElement.Contains(","))
                {
                    aPointCoordinate = sElement.Split(',');

                    sX = aPointCoordinate[0];
                    sY = aPointCoordinate[1];

                    point = new PointD(Double.Parse(sX, CultureInfo.InvariantCulture), Double.Parse(sY, CultureInfo.InvariantCulture));
                    aPoint.Add(point);
                }
                else
                {
                    aPoint = GetCoordinatesFromElementWithoutComma(_sElement);
                    break;
                }
            }
            return aPoint;
        }

        /// <summary>
        /// Get Coordinates From elements without commas
        /// </summary>
        /// <param name="_sElement"></param>
        /// <returns></returns>
        public static List<PointD> GetCoordinatesFromElementWithoutComma(string _sElement)
        {
            List<PointD> aPoint = new List<PointD>();

            string[] aElementTemp = _sElement.Split(' ');
            string sX = string.Empty;
            string sY = string.Empty;
            for (int i = 0; i < aElementTemp.Length; i++)
            {
                sX = aElementTemp[i];
                i++;
                sY = aElementTemp[i];
                aPoint.Add(new PointD(Double.Parse(sX, CultureInfo.InvariantCulture), Double.Parse(sY, CultureInfo.InvariantCulture)));
            }
            return aPoint;
        }

        /// <summary>
        /// Creates the style.
        /// </summary>
        /// <param name="_node">The _node.</param>
        /// <returns></returns>
        private static Style CreateStyle(XmlNode _node)
        {
            string sFill = string.Empty;
            string sStroke = string.Empty;
            string sStrokeWidth = string.Empty;
            Style style = new Style();

            int nFoundAttributes = 0;
            if (HasAttribute(_node, "fill"))
            {
                sFill = _node.Attributes["fill"].Value;
                style.FillColor = GetColor(sFill);
                nFoundAttributes++;
            }

            if (HasAttribute(_node, "stroke"))
            {
                sStroke = _node.Attributes["stroke"].Value;
                style.StrokeColor = GetColor(sStroke);
                nFoundAttributes++;
            }

            if (HasAttribute(_node, "stroke-width"))
            {
                sStrokeWidth = _node.Attributes["stroke-width"].Value;
                style.StrokeWidth = float.Parse(sStrokeWidth, CultureInfo.InvariantCulture);
                nFoundAttributes++;
            }

            if (nFoundAttributes == 0)
            {
                style.FillColor = Color.Black;
                style.StrokeColor = Color.Black;
            }

            return style;
        }

        /// <summary>
        /// Determines whether the specified _node has attribute.
        /// </summary>
        /// <param name="_node">The _node.</param>
        /// <param name="_sAttributeName">Name of the _s attribute.</param>
        /// <returns>
        /// 	<c>true</c> if the specified _node has attribute; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasAttribute(XmlNode _node, string _sAttributeName)
        {
            XmlAttribute xmlAttribute = _node.Attributes[_sAttributeName];
            return xmlAttribute != null;
        }

        /// <summary>
        /// Parses the style.
        /// </summary>
        /// <param name="_sStyle">The _s style.</param>
        /// <returns></returns>
        private static Style CreateStyle(string _sStyle)
        {
            Style style = new Style();
            string[] aStyle = _sStyle.Split(';');
            List<KeyValuePair<string, string>> aStyleCommand = new List<KeyValuePair<string, string>>();
            foreach (string sStyle in aStyle)
            {
                Color fillColor = Color.Transparent;

                if (string.IsNullOrEmpty(sStyle)) continue;
                string[] aStyleElement = sStyle.Split(':');
                string sName = aStyleElement[0].Trim();
                string sValue = aStyleElement[1];
                aStyleCommand.Add(new KeyValuePair<string, string>(sName, sValue));
                if (sName == "fill")
                {
                    fillColor = GetColor(sValue);
                    style.FillColor = fillColor;
                }
                else if (sName == "fill-rule")
                {
                    style.FillRule = sValue == FillRule.EvenOdd.ToString().ToLower() ? FillRule.EvenOdd : FillRule.NonZero;
                }
                else if (sName == "fill-opacity")
                {
                    style.FillOpacity = float.Parse(sValue, CultureInfo.InvariantCulture);
                }
                else if (sName == "stroke")
                {
                    style.StrokeColor = GetColor(sValue);
                }
                else if (sName == "stroke-width")
                {
                    if (sValue.Contains("px"))
                    {
                        string sValueTemp = sValue.Substring(0, sValue.Length - 2);
                        style.StrokeWidth = float.Parse(sValueTemp, CultureInfo.InvariantCulture);
                        Debug.WriteLine(string.Format("px in stroke-width: {0}", style.StrokeWidth));
                    }
                    else
                    {
                        style.StrokeWidth = (int)Convert.ToDouble(sValue, CultureInfo.InvariantCulture);
                    }
                }
                else if (sName == "stroke-linecap")
                {
                    LineCap lineCap = LineCap.AnchorMask;
                    if (sValue != "butt")
                    {
                        sValue = sValue.Substring(0, 1).ToUpper() + sValue.Substring(1, sValue.Length - 1);
                        lineCap = (LineCap)Enum.Parse(typeof(LineCap), sValue);
                        style.LineCap = lineCap;
                    }
                    else
                    {
                        lineCap = LineCap.DiamondAnchor;
                    }
                }
                else if (sName == "stroke-linejoin")
                {
                }
                else if (sName == "stroke-opacity")
                {
                }
            }
            return style;
        }

        /// <summary>
        /// Gets the color.
        /// </summary>
        /// <param name="sValue">The s value.</param>
        /// <returns></returns>
        private static Color GetColor(string sValue)
        {
            Color fillColor = Color.Transparent;
            if (sValue.StartsWith("#"))
            {
                //if we got only 3 color chars, extend them. (i.e. double them as suggested in svg document)
                string sColor = string.Empty;
                if (sValue.Length == 4)
                {
                    if (Regex.Match(sValue, "#[0-9a-fA-F]").Success)
                    {
                        sColor = sValue.Substring(1, sValue.Length - 1);
                        sColor = string.Format("{0}{0}{1}{1}{2}{2}", sColor[0], sColor[1], sColor[2]);
                    }
                }
                else
                {
                    sColor = sValue.Substring(1, sValue.Length - 1);
                }
                fillColor = ColorTranslator.FromHtml("#" + sColor);
            }
            else if (sValue.StartsWith("rgb"))
            {
                //TODO
                if (sValue.Contains("%"))
                {
                }
                else
                {
                    sValue = sValue.Replace("rgb", "");
                    sValue = sValue.Replace("(", "");
                    sValue = sValue.Replace(")", "");
                    string[] aValue = sValue.Split(',');
                    int r = Convert.ToInt32(aValue[0]);
                    int g = Convert.ToInt32(aValue[1]);
                    int b = Convert.ToInt32(aValue[2]);
                    fillColor = Color.FromArgb(r, g, b);
                }
                //not yet implemented
            }
            else
            {
                if (sValue != "none")
                {
                    //style.FillColor = 
                    KnownColor kc = ColorFunctions.GetKnownColorByName(sValue);
                    fillColor = Color.FromKnownColor(kc);
                }
            }
            return fillColor;
        }

        /// <summary>
        /// Creates the circle.
        /// </summary>
        /// <param name="_xmlNode">The _XML node.</param>
        /// <param name="_element">The _element.</param>
        /// <returns></returns>
        private static Element CreateCircle(XmlNode _xmlNode, Element _element)
        {
            float cx = float.Parse(_xmlNode.Attributes["cx"].Value, CultureInfo.InvariantCulture);
            float cy = float.Parse(_xmlNode.Attributes["cy"].Value, CultureInfo.InvariantCulture);
            float r = float.Parse(_xmlNode.Attributes["r"].Value, CultureInfo.InvariantCulture);

            Circle circle = new Circle(cx, cy, r);
            XmlAttribute xmlAttributeStyle = _xmlNode.Attributes["style"];

            if (xmlAttributeStyle != null)
            {
                Style style = CreateStyle(xmlAttributeStyle.Value);
                circle.Style = style;
            }

            if (_xmlNode.Attributes["fill"] != null)
            {
                circle.Style.FillColor = GetColor(_xmlNode.Attributes["fill"].Value);
            }

            return circle;
        }

        /// <summary>
        /// Creates the ellipse.
        /// </summary>
        /// <param name="_xmlNode">The _XML node.</param>
        /// <param name="_element">The _element.</param>
        /// <returns></returns>
        private static Element CreateEllipse(XmlNode _xmlNode, Element _element)
        {
            float cx = float.Parse(_xmlNode.Attributes["cx"].Value, CultureInfo.InvariantCulture);
            float cy = float.Parse(_xmlNode.Attributes["cy"].Value, CultureInfo.InvariantCulture);
            float rx = float.Parse(_xmlNode.Attributes["rx"].Value, CultureInfo.InvariantCulture);
            float ry = float.Parse(_xmlNode.Attributes["ry"].Value, CultureInfo.InvariantCulture);
            Ellipse ellipse = new Ellipse(cx, cy, rx, ry);
            return ellipse;
        }

        /// <summary>
        /// Creates the polygon.
        /// </summary>
        /// <param name="_xmlNode">The _XML node.</param>
        /// <param name="_element">The _element.</param>
        /// <returns></returns>
        private static Element CreatePolygon(XmlNode _xmlNode, Element _element)
        {
            Polygon polygon = new Polygon();

            XmlAttribute xmlAttribute = _xmlNode.Attributes["style"];

            Style style = null;
            if (xmlAttribute != null)
            {
                string sStyle = _xmlNode.Attributes["style"].Value;
                style = CreateStyle(sStyle);
            }
            else
            {
                style = CreateStyle(_xmlNode);
            }
            polygon.Style = style;

            string sElement = _xmlNode.Attributes["points"].Value;
            List<PointD> aPoint = GetCoordinatesFromElement(sElement);
            polygon.Points = aPoint;
            return polygon;
        }

        /// <summary>
        /// Creates the poly line.
        /// </summary>
        /// <param name="_xmlNode">The _XML node.</param>
        /// <param name="_element">The _element.</param>
        /// <returns></returns>
        private static Element CreatePolyline(XmlNode _xmlNode, Element _element)
        {
            string sStyle = _xmlNode.Attributes["style"].Value;

            Style style = CreateStyle(sStyle);

            string sElement = _xmlNode.Attributes["points"].Value;
            List<PointD> aPoint = GetCoordinatesFromElement(sElement);
            Polyline polyline = new Polyline();
            polyline.Points = aPoint;
            polyline.Style = style;
            return polyline;
        }

        /// <summary>
        /// Texts the specified _XML node.
        /// </summary>
        /// <param name="_xmlNode">The _XML node.</param>
        /// <param name="_element">The _element.</param>
        /// <returns></returns>
        private static Element CreateText(XmlNode _xmlNode, Element _element)
        {
            Text text = new Text();
            return text;
        }

        /// <summary>
        /// Creates the line.
        /// </summary>
        /// <param name="_xmlNode">The _XML node.</param>
        /// <param name="_element">The _element.</param>
        /// <returns></returns>
        private static Element CreateLine(XmlNode _xmlNode, Element _element)
        {
            string sX1 = _xmlNode.Attributes["x1"].Value;
            float fX1 = float.Parse(sX1, CultureInfo.InvariantCulture);

            string sY1 = _xmlNode.Attributes["y1"].Value;
            float fY1 = float.Parse(sY1, CultureInfo.InvariantCulture);

            string sX2 = _xmlNode.Attributes["x2"].Value;
            float fX2 = float.Parse(sX2, CultureInfo.InvariantCulture);

            string sY2 = _xmlNode.Attributes["y2"].Value;
            float fY2 = float.Parse(sY2, CultureInfo.InvariantCulture);

            Line line = new Line(fX1, fY1, fX2, fY2);

            string sStyle = _xmlNode.Attributes["style"].Value;

            Style style = CreateStyle(sStyle);
            line.Style = style;
            return line;
        }

        /// <summary>
        /// Creates the rectangle.
        /// </summary>
        /// <param name="_xmlNode">The _XML node.</param>
        /// <param name="_element">The _element.</param>
        /// <returns></returns>
        private static Element CreateRectangle(XmlNode _xmlNode, Element _element)
        {
            float x = float.Parse(_xmlNode.Attributes["x"].Value, CultureInfo.InvariantCulture);
            float y = float.Parse(_xmlNode.Attributes["y"].Value, CultureInfo.InvariantCulture);
            float width = float.Parse(_xmlNode.Attributes["width"].Value, CultureInfo.InvariantCulture);
            float height = float.Parse(_xmlNode.Attributes["height"].Value, CultureInfo.InvariantCulture);

            float rx = -1;
            if (HasAttribute(_xmlNode, "rx"))
            {
                rx = float.Parse(_xmlNode.Attributes["rx"].Value, CultureInfo.InvariantCulture);
            }
            float ry = -1;
            if (HasAttribute(_xmlNode, "ry"))
            {
                ry = float.Parse(_xmlNode.Attributes["ry"].Value, CultureInfo.InvariantCulture);
            }
            Rectangle rectangle = new Rectangle(x, y, width, height, rx, ry);

            if (HasAttribute(_xmlNode, "style"))
            {
                string sStyle = _xmlNode.Attributes["style"].Value;
                Style style = CreateStyle(sStyle);
                rectangle.Style = style;
            }
            return rectangle;
        }
    }
}
